package ws_server

import (
	"github.com/gorilla/websocket"
	"net/http"
	"sync"
	"time"
)

const (
	defaultMaxConnectionIdle = 0.5 * 60 * 60 * time.Second
)

type WSKeepAlive struct {
	*websocket.Conn   // 嵌入websocket.Conn，实现了websocket.Conn的所有方法
	Server            *Server
	idle              time.Time     // 最后一次活动的时间戳
	maxConnectionIdle time.Duration // 连接允许的最大空闲时间
	done              chan struct{} // 发送关闭连接信号的通道
	idleMu            sync.Mutex
	Uid               string // 保存该连接中用户的uid

	msgMu          sync.Mutex
	ackQueue       []*Message          // 待ack的消息所在队列
	msgAckRegister map[string]*Message // 建立消息id和消息的映射
	msgChan        chan *Message       // readAck 和 handlerWrite协程之间通过chan通信
}

func NewWSKeepAlive(s *Server, w http.ResponseWriter, r *http.Request) *WSKeepAlive {
	c, err := s.UpGrader.Upgrade(w, r, nil)
	if err != nil {
		s.Errorf("heart beat server upgrade err: %v", err)
		return nil
	}

	conn := &WSKeepAlive{
		Conn:              c,
		Server:            s,
		idle:              time.Now(),
		maxConnectionIdle: s.Opt.maxConnectionIdle,
		done:              make(chan struct{}),

		ackQueue:       make([]*Message, 0, 2),       // 用于存放待ack的消息的队列
		msgAckRegister: make(map[string]*Message, 2), // 通过消息id能获取到消息的AckSeq、FrameType等信息
		msgChan:        make(chan *Message, 1),       // 容量为1，保证收发的顺序问题
	}

	// 启动心跳检测
	go conn.KeepAlive()

	return conn
}

// ReadMessage 重写websocket.Conn的ReadMessage方法
func (conn *WSKeepAlive) ReadMessage() (messageType int, p []byte, err error) {
	messageType, p, err = conn.Conn.ReadMessage()
	conn.idleMu.Lock()
	defer conn.idleMu.Unlock()
	// 当接收到客户端消息时，连接被视为活跃的，不应计算空闲超时
	conn.idle = time.Time{} // 使用零值时间表示当前连接处于活跃
	return
}

// WriteMessage 重写websocket.Conn的WriteMessage方法
func (conn *WSKeepAlive) WriteMessage(messageType int, data []byte) (err error) {
	conn.idleMu.Lock()
	defer conn.idleMu.Unlock()

	err = conn.Conn.WriteMessage(messageType, data) // 并发写线程不安全
	// 发送消息后，服务器需要开始计算客户端的响应时间
	conn.idle = time.Now() // 记录了最后一次发送消息的确切时间点
	return
}

// Close 重写websocket.Conn的Close方法
func (conn *WSKeepAlive) Close() error {
	select {
	case <-conn.done:
	default:
		close(conn.done)
	}
	return conn.Conn.Close()
}

// KeepAlive 心跳检测机制
func (conn *WSKeepAlive) KeepAlive() {
	idleTimer := time.NewTimer(conn.maxConnectionIdle) // 设定定时器定期触发检查事件（闹钟）
	defer func() {
		idleTimer.Stop()
	}()
	for {
		select {
		case <-idleTimer.C:
			conn.idleMu.Lock() // 使用锁保证idle变量并发下的安全访问
			idle := conn.idle

			if idle.IsZero() {
				conn.idleMu.Unlock()
				idleTimer.Reset(conn.maxConnectionIdle) // idle为零值时间，连接非空闲，idleTimer定时器重新定时
				continue
			}

			val := conn.maxConnectionIdle - time.Since(idle) // 计算连接从最后一次活跃到现在时间差是否超过最大时间差
			conn.idleMu.Unlock()
			if val <= 0 {
				// 连接不活跃且超时，关闭连接
				conn.Server.Close(conn)
				return
			} else {
				idleTimer.Reset(val) // 连接不活跃但未超时，设定定时器为剩余时间
			}
		case <-conn.done:
			return
		}
	}
}

// InsertAckQueue 服务端向客户端发送ack消息时，可能需要客户端对该ack进行确认，将该ack消息放入队列中等待客户端的确认
func (conn *WSKeepAlive) InsertAckQueue(msg *Message) {
	conn.msgMu.Lock()
	defer conn.msgMu.Unlock()

	// 如果消息已经加入了映射表
	if cachedMsg, ok := conn.msgAckRegister[msg.Id]; ok {
		// 虽然映射表中有记录，但队列已经处理完了该消息，直接返回
		if len(conn.ackQueue) == 0 {
			return
		}
		// 如果映射表中已存在消息的 AckSeq 大于或等于新收到消息的 AckSeq，表明这个新消息是重复确认或者过期的确认，直接返回
		if cachedMsg.AckSeq >= msg.AckSeq {
			return
		}
		// 如果新消息的 AckSeq 更大，用新消息更新映射表中的记录
		conn.msgAckRegister[msg.Id] = msg
		return
	}

	// 消息不在映射表中，为新消息
	// 如果消息是确认帧但没有对应的原始消息映射记录，则直接丢弃，因为无法确认是哪条消息的确认，可能是客户端重复发送的确认消息
	if msg.FrameType == FrameAck {
		return
	}
	// 新的非确认帧消息，将消息插入队列，在映射表中记录该消息
	conn.ackQueue = append(conn.ackQueue, msg)
	conn.msgAckRegister[msg.Id] = msg
}
